#include <assemble.h>
#include "process.h"
#include "layout.h"
#include "disk.h"
#include "tty.h"


/* hd controller regs. */
#define HD_CMD		0x3f6

#define HD_DATA		0x1f0
#define HD_ERROR	0x1f1
#define HD_NSECTOR	0x1f2
#define HD_SECTOR	0x1f3
#define HD_LCYL		0x1f4
#define HD_HCYL		0x1f5
#define HD_CURRENT	0x1f6
#define HD_STATUS	0x1f7

/* Bits of HD_STATUS */
#define ERR_STAT	0x01
#define INDEX_STAT	0x02
#define ECC_STAT	0x04
#define DRQ_STAT	0x08
#define SEEK_STAT	0x10
#define WRERR_STAT	0x20
#define READY_STAT	0x40
#define BUSY_STAT	0x80

u32_t num_sect_disk;
struct disk_info_s {
	int head, sect, cyl, wpcom, lzone, ctl;
} disk_info;

/* Save current request's information. */
static int	cmd_g;
static u8_t	*buf_g;
static u32_t	count_g;
static struct process **wait_g;

/* Disk driver is busy or not. */
static int busy_request;
/* If busy, put the process here to wait. */
static struct process *wait_request;

void init_disk()
{
	disk_info.cyl	= *(unsigned short *) INFO_DISK;
	disk_info.head	= *(unsigned char *) (INFO_DISK + 2);
	disk_info.wpcom	= *(unsigned short *) (INFO_DISK + 5);
	disk_info.ctl	= *(unsigned char *) (INFO_DISK + 8);
	disk_info.lzone	= *(unsigned short *) (INFO_DISK + 12);
	disk_info.sect	= *(unsigned short *) (INFO_DISK + 14);

	num_sect_disk = disk_info.cyl * disk_info.head * disk_info.sect;
	printk("disk(sector): %d\n", num_sect_disk);
}

static int win_result()
{
	int i = in_a(HD_STATUS);

	if((i & (BUSY_STAT | READY_STAT | WRERR_STAT | SEEK_STAT | ERR_STAT))
		       == (READY_STAT | SEEK_STAT))
		return 0;

	if(i & 1)
		i = in_a(HD_ERROR);
	return 1;
}

/* Interupt handle for disk. */
/* XXX: Now in interupt, the context doesn't belongs to the
 * process that asked the request, so @buf_g is not
 * the right address. */
void win_int_handle()
{
	if(win_result()) {
		write_tty("error in disk handle!\n");
		return;
	}

	if(cmd_g == WIN_READ)
		port_read(buf_g, SIZE_SECTOR);

	/* End request. */
	if(--count_g == 0) {
		wake_wait(wait_g);
		busy_request = 0;
		wake_wait(&wait_request);
		return;
	}

	buf_g += SIZE_SECTOR;
	if(cmd_g == WIN_WRITE)
		port_write(buf_g, SIZE_SECTOR);
}

static void send_cmd(u32_t nr, int count, u32_t cmd)
{
	int retrys = 10000;
	u32_t head, cyl, sect;

	sect = nr % disk_info.sect + 1;
	nr /= disk_info.sect;
	head = nr % disk_info.head;
	nr /= disk_info.head;
	cyl = nr;	// nr % disk_info.cyl

	while(--retrys && (in_a(HD_STATUS) & 0xC0) != 0x40);

	if(retrys == 0) {
		write_tty("error in hd \n");
		return;
	}

	out_a(HD_CMD, disk_info.ctl);

	out_a(HD_ERROR, disk_info.wpcom >> 2);
	out_a(HD_NSECTOR, count);
	out_a(HD_SECTOR, sect);
	out_a(HD_LCYL, cyl);
	out_a(HD_HCYL, cyl >> 8);
	out_a(HD_CURRENT, 0xA0 | head);
	out_a(HD_STATUS, cmd);
}

int rdwr_disk(u32_t begin, int count, u8_t *buf, int cmd, struct process **wait)
{
	int retrys = 3000;

	/* Busy. */
	if(busy_request == 1)
		wait_for(&wait_request);

	/* Not busy. */
	busy_request = 1;
	cmd_g = cmd;
	count_g = count;
	buf_g = buf;
	wait_g = wait;

	send_cmd(begin, count, cmd);

	if(cmd == WIN_WRITE) {
		while(--retrys && !(in_a(HD_STATUS) & DRQ_STAT));
		if(retrys == 0) {
			write_tty("error in write disk!\n");
			return 1;
		}
		port_write(buf, SIZE_SECTOR);
	}

	/* XXX: we should avoid race condition to wait interupt. */
	if(busy_request == 1)
		pause();
	return 0;
}
